React Testing Library๋ก React ์ปดํฌ๋ํธ ํ ์คํ ์ ๋ง์คํฐํ์ธ์. ์ฌ์ฉ์ ํ๋๊ณผ ์ ๊ทผ์ฑ์ ์ด์ ์ ๋ง์ถ ์ ์ง๋ณด์ ๊ฐ๋ฅํ๊ณ ํจ๊ณผ์ ์ธ ํ ์คํธ ์์ฑ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐฐ์๋ณด์ธ์.
React Testing Library: ๊ธ๋ก๋ฒ ํ์ ์ํ ์ปดํฌ๋ํธ ํ ์คํ ๋ชจ๋ฒ ์ฌ๋ก
๋์์์ด ์งํํ๋ ์น ๊ฐ๋ฐ ์ธ๊ณ์์ React ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ๋ขฐ์ฑ๊ณผ ํ์ง์ ๋ณด์ฅํ๋ ๊ฒ์ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ด๋ ๋ค์ํ ์ฌ์ฉ์ ๊ธฐ๋ฐ๊ณผ ์ ๊ทผ์ฑ ์๊ตฌ์ฌํญ์ ๊ฐ์ง ํ๋ก์ ํธ๋ฅผ ์งํํ๋ ๊ธ๋ก๋ฒ ํ์๊ฒ ํนํ ๊ทธ๋ ์ต๋๋ค. React Testing Library(RTL)๋ ์ปดํฌ๋ํธ ํ ์คํ ์ ๊ฐ๋ ฅํ๊ณ ์ฌ์ฉ์ ์ค์ฌ์ ์ธ ์ ๊ทผ ๋ฐฉ์์ ์ ๊ณตํฉ๋๋ค. ๊ตฌํ ์ธ๋ถ ์ฌํญ์ ์ด์ ์ ๋ง์ถ๋ ๊ธฐ์กด์ ํ ์คํ ๋ฐฉ๋ฒ๊ณผ ๋ฌ๋ฆฌ, RTL์ ์ฌ์ฉ์๊ฐ ์ปดํฌ๋ํธ์ ์ํธ์์ฉํ๋ ๊ฒ์ฒ๋ผ ํ ์คํธํ๋๋ก ์ฅ๋ คํ์ฌ ๋ ๊ฒฌ๊ณ ํ๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ฌ์ด ํ ์คํธ๋ฅผ ๋ง๋ค ์ ์๊ฒ ํฉ๋๋ค. ์ด ์ข ํฉ ๊ฐ์ด๋์์๋ ๊ธ๋ก๋ฒ ์ฌ์ฉ์๋ฅผ ์ํ ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์ถ์ ์ด์ ์ ๋ง์ถฐ React ํ๋ก์ ํธ์์ RTL์ ํ์ฉํ๊ธฐ ์ํ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์์ธํ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์ React Testing Library๋ฅผ ์ฌ์ฉํด์ผ ํ ๊น์?
๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ดํด๋ณด๊ธฐ ์ ์, ์ RTL์ด ๋ค๋ฅธ ํ ์คํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ฐจ๋ณํ๋๋์ง ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ๋ค์์ ๋ช ๊ฐ์ง ์ฃผ์ ์ฅ์ ์ ๋๋ค:
- ์ฌ์ฉ์ ์ค์ฌ ์ ๊ทผ ๋ฐฉ์: RTL์ ์ฌ์ฉ์ ๊ด์ ์์ ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ๋ ๊ฒ์ ์ฐ์ ์ํฉ๋๋ค. ์ฌ์ฉ์๊ฐ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋์ผํ ๋ฐฉ์(์: ๋ฒํผ ํด๋ฆญ, ์ ๋ ฅ ํ๋์ ํ์ดํ)์ผ๋ก ์ปดํฌ๋ํธ์ ์ํธ์์ฉํ์ฌ ๋ ํ์ค์ ์ด๊ณ ์ ๋ขฐํ ์ ์๋ ํ ์คํ ๊ฒฝํ์ ๋ณด์ฅํฉ๋๋ค.
- ์ ๊ทผ์ฑ ์ค์ฌ: RTL์ ์ฅ์ ๋ฅผ ๊ฐ์ง ์ฌ์ฉ์๋ฅผ ๊ณ ๋ คํ๋ ๋ฐฉ์์ผ๋ก ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ๋๋ก ์ฅ๋ คํ์ฌ ์ ๊ทผ์ฑ ์๋ ์ปดํฌ๋ํธ ์์ฑ์ ์ด์งํฉ๋๋ค. ์ด๋ WCAG์ ๊ฐ์ ๊ธ๋ก๋ฒ ์ ๊ทผ์ฑ ํ์ค๊ณผ ์ผ์นํฉ๋๋ค.
- ์ ์ง๋ณด์ ๋น์ฉ ๊ฐ์: ๊ตฌํ ์ธ๋ถ ์ฌํญ(์: ๋ด๋ถ ์ํ, ํน์ ํจ์ ํธ์ถ) ํ ์คํธ๋ฅผ ํผํจ์ผ๋ก์จ, RTL ํ ์คํธ๋ ์ฝ๋๋ฅผ ๋ฆฌํฉํ ๋งํ ๋ ๊นจ์ง ๊ฐ๋ฅ์ฑ์ด ์ ์ต๋๋ค. ์ด๋ ๋ ์ ์ง๋ณด์ํ๊ธฐ ์ฝ๊ณ ๋ณต์๋ ฅ ์๋ ํ ์คํธ๋ก ์ด์ด์ง๋๋ค.
- ๊ฐ์ ๋ ์ฝ๋ ์ค๊ณ: RTL์ ์ฌ์ฉ์ ์ค์ฌ ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉ์๊ฐ ์ปดํฌ๋ํธ์ ์ด๋ป๊ฒ ์ํธ์์ฉํ ์ง ์๊ฐํ๊ฒ ํ๋ฏ๋ก, ์ข ์ข ๋ ๋์ ์ปดํฌ๋ํธ ์ค๊ณ๋ก ์ด์ด์ง๋๋ค.
- ์ปค๋ฎค๋ํฐ์ ์ํ๊ณ: RTL์ ํฌ๊ณ ํ๋ฐํ ์ปค๋ฎค๋ํฐ๋ฅผ ์๋ํ๋ฉฐ, ํ๋ถํ ๋ฆฌ์์ค, ์ง์, ํ์ฅ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
ํ ์คํ ํ๊ฒฝ ์ค์ ํ๊ธฐ
RTL์ ์์ํ๋ ค๋ฉด ํ ์คํ ํ๊ฒฝ์ ์ค์ ํด์ผ ํฉ๋๋ค. ๋ค์์ Jest์ RTL์ด ์ฌ์ ๊ตฌ์ฑ๋ Create React App(CRA)์ ์ฌ์ฉํ ๊ธฐ๋ณธ ์ค์ ์ ๋๋ค:
npx create-react-app my-react-app
cd my-react-app
npm install --save-dev @testing-library/react @testing-library/jest-dom
์ค๋ช :
- `npx create-react-app my-react-app`: Create React App์ ์ฌ์ฉํ์ฌ ์ React ํ๋ก์ ํธ๋ฅผ ์์ฑํฉ๋๋ค.
- `cd my-react-app`: ์๋ก ์์ฑ๋ ํ๋ก์ ํธ ๋๋ ํ ๋ฆฌ๋ก ์ด๋ํฉ๋๋ค.
- `npm install --save-dev @testing-library/react @testing-library/jest-dom`: ํ์ํ RTL ํจํค์ง๋ฅผ ๊ฐ๋ฐ ์์กด์ฑ์ผ๋ก ์ค์นํฉ๋๋ค. `@testing-library/react`๋ ํต์ฌ RTL ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ณ , `@testing-library/jest-dom`์ DOM ์์ ์ ์ํ ์ ์ฉํ Jest ๋งค์ฒ๋ฅผ ์ ๊ณตํฉ๋๋ค.
CRA๋ฅผ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ, Jest์ RTL์ ๋ณ๋๋ก ์ค์นํ๊ณ RTL์ ์ฌ์ฉํ๋๋ก Jest๋ฅผ ๊ตฌ์ฑํด์ผ ํฉ๋๋ค.
React Testing Library๋ฅผ ์ฌ์ฉํ ์ปดํฌ๋ํธ ํ ์คํ ๋ชจ๋ฒ ์ฌ๋ก
1. ์ฌ์ฉ์ ์ํธ์์ฉ์ ๋ชจ๋ฐฉํ๋ ํ ์คํธ ์์ฑํ๊ธฐ
RTL์ ํต์ฌ ์์น์ ์ฌ์ฉ์๊ฐ ํ๋ ๊ฒ์ฒ๋ผ ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ๋ ๊ฒ์ ๋๋ค. ์ด๋ ๋ด๋ถ ๊ตฌํ ์ธ๋ถ ์ฌํญ๋ณด๋ค๋ ์ฌ์ฉ์๊ฐ ๋ณด๊ณ ์ํํ๋ ๊ฒ์ ์ด์ ์ ๋ง์ถ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. RTL์ด ์ ๊ณตํ๋ `screen` ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ, ์ญํ ๋๋ ์ ๊ทผ์ฑ ๋ ์ด๋ธ์ ๊ธฐ๋ฐ์ผ๋ก ์์๋ฅผ ์ฟผ๋ฆฌํ์ธ์.
์์: ๋ฒํผ ํด๋ฆญ ํ ์คํธํ๊ธฐ
๊ฐ๋จํ ๋ฒํผ ์ปดํฌ๋ํธ๊ฐ ์๋ค๊ณ ๊ฐ์ ํด ๋ด ์๋ค:
// Button.js
import React from 'react';
function Button({ onClick, children }) {
return ;
}
export default Button;
RTL์ ์ฌ์ฉํ์ฌ ํ ์คํธํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
// Button.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
describe('Button Component', () => {
it('ํด๋ฆญ ์ onClick ํธ๋ค๋ฌ๋ฅผ ํธ์ถํ๋ค', () => {
const handleClick = jest.fn();
render();
const buttonElement = screen.getByText('Click Me');
fireEvent.click(buttonElement);
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
์ค๋ช :
- `render()`: ๋ชจ์ `onClick` ํธ๋ค๋ฌ๋ก Button ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํฉ๋๋ค.
- `screen.getByText('Click Me')`: "Click Me" ํ ์คํธ๋ฅผ ํฌํจํ๋ ์์๋ฅผ ๋ฌธ์์์ ์ฟผ๋ฆฌํฉ๋๋ค. ์ด๊ฒ์ด ์ฌ์ฉ์๊ฐ ๋ฒํผ์ ์๋ณํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
- `fireEvent.click(buttonElement)`: ๋ฒํผ ์์์์ ํด๋ฆญ ์ด๋ฒคํธ๋ฅผ ์๋ฎฌ๋ ์ด์ ํฉ๋๋ค.
- `expect(handleClick).toHaveBeenCalledTimes(1)`: `onClick` ํธ๋ค๋ฌ๊ฐ ํ ๋ฒ ํธ์ถ๋์๋์ง ํ์ธํฉ๋๋ค.
์ด๊ฒ์ด ๊ตฌํ ์ธ๋ถ ์ฌํญ์ ํ ์คํธํ๋ ๊ฒ๋ณด๋ค ๋์ ์ด์ : Button ์ปดํฌ๋ํธ๋ฅผ ๋ค๋ฅธ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ๋ด๋ถ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋๋ก ๋ฆฌํฉํ ๋งํ๋ค๊ณ ์์ํด ๋ณด์ธ์. ํน์ ์ด๋ฒคํธ ํธ๋ค๋ฌ ํจ์๋ฅผ ํ ์คํธํ๋ค๋ฉด ํ ์คํธ๊ฐ ๊นจ์ก์ ๊ฒ์ ๋๋ค. ์ฌ์ฉ์ ์ํธ์์ฉ(๋ฒํผ ํด๋ฆญ)์ ์ด์ ์ ๋ง์ถค์ผ๋ก์จ, ํ ์คํธ๋ ๋ฆฌํฉํ ๋ง ํ์๋ ์ ํจํ๊ฒ ์ ์ง๋ฉ๋๋ค.
2. ์ฌ์ฉ์ ์๋์ ๊ธฐ๋ฐํ ์ฟผ๋ฆฌ ์ฐ์ ์์ ์ ํ๊ธฐ
RTL์ ์์๋ฅผ ์ฐพ๊ธฐ ์ํ ๋ค์ํ ์ฟผ๋ฆฌ ๋ฉ์๋๋ฅผ ์ ๊ณตํฉ๋๋ค. ๋ค์ ์ฟผ๋ฆฌ๋ค์ ์ฌ์ฉ์๊ฐ ์ปดํฌ๋ํธ๋ฅผ ์ธ์ํ๊ณ ์ํธ์์ฉํ๋ ๋ฐฉ์์ ๊ฐ์ฅ ์ ๋ฐ์ํ๋ฏ๋ก ์ด ์์๋๋ก ์ฐ์ ์์๋ฅผ ์ ํ์ธ์:
- getByRole: ์ด ์ฟผ๋ฆฌ๋ ๊ฐ์ฅ ์ ๊ทผ์ฑ์ด ๋์ผ๋ฉฐ ์ฒซ ๋ฒ์งธ ์ ํ์ด์ด์ผ ํฉ๋๋ค. ARIA ์ญํ (์: button, link, heading)์ ๊ธฐ๋ฐ์ผ๋ก ์์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.
- getByLabelText: ์ ๋ ฅ ํ๋์ ๊ฐ์ ํน์ ๋ ์ด๋ธ๊ณผ ์ฐ๊ด๋ ์์๋ฅผ ์ฐพ๋ ๋ฐ ์ฌ์ฉํฉ๋๋ค.
- getByPlaceholderText: ํ๋ ์ด์คํ๋ ํ ์คํธ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ๋ ฅ ํ๋๋ฅผ ์ฐพ๋ ๋ฐ ์ฌ์ฉํฉ๋๋ค.
- getByText: ํ ์คํธ ์ฝํ ์ธ ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์๋ฅผ ์ฐพ๋ ๋ฐ ์ฌ์ฉํฉ๋๋ค. ์ฌ๋ฌ ๊ณณ์ ๋ํ๋ ์ ์๋ ์ผ๋ฐ์ ์ธ ํ ์คํธ๋ ํผํ๊ณ ๊ตฌ์ฒด์ ์ผ๋ก ์ฌ์ฉํ์ธ์.
- getByDisplayValue: ํ์ฌ ๊ฐ์ ๊ธฐ๋ฐ์ผ๋ก ์ ๋ ฅ ํ๋๋ฅผ ์ฐพ๋ ๋ฐ ์ฌ์ฉํฉ๋๋ค.
์์: ํผ ์ ๋ ฅ ํ ์คํธํ๊ธฐ
// Input.js
import React from 'react';
function Input({ label, placeholder, value, onChange }) {
return (
);
}
export default Input;
๊ถ์ฅ ์ฟผ๋ฆฌ ์์๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
// Input.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import Input from './Input';
describe('Input Component', () => {
it('์ฌ์ฉ์๊ฐ ์
๋ ฅํ ๋ ๊ฐ์ด ์
๋ฐ์ดํธ๋๋ค', () => {
const handleChange = jest.fn();
render();
const inputElement = screen.getByLabelText('Name');
fireEvent.change(inputElement, { target: { value: 'John Doe' } });
expect(handleChange).toHaveBeenCalledTimes(1);
expect(handleChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: 'John Doe' } }));
});
});
์ค๋ช :
- `screen.getByLabelText('Name')`: `getByLabelText`๋ฅผ ์ฌ์ฉํ์ฌ "Name" ๋ ์ด๋ธ๊ณผ ์ฐ๊ด๋ ์ ๋ ฅ ํ๋๋ฅผ ์ฐพ์ต๋๋ค. ์ด๊ฒ์ด ์ ๋ ฅ์ ์ฐพ๋ ๊ฐ์ฅ ์ ๊ทผ์ฑ ์๊ณ ์ฌ์ฉ์ ์นํ์ ์ธ ๋ฐฉ๋ฒ์ ๋๋ค.
3. ๊ตฌํ ์ธ๋ถ ์ฌํญ ํ ์คํธ ํผํ๊ธฐ
์์ ์ธ๊ธํ๋ฏ์ด, ๋ด๋ถ ์ํ, ํจ์ ํธ์ถ ๋๋ ํน์ CSS ํด๋์ค๋ฅผ ํ ์คํธํ๋ ๊ฒ์ ํผํ์ธ์. ์ด๊ฒ๋ค์ ๋ณ๊ฒฝ๋ ์ ์๊ณ ๊นจ์ง๊ธฐ ์ฌ์ด ํ ์คํธ๋ก ์ด์ด์ง ์ ์๋ ๊ตฌํ ์ธ๋ถ ์ฌํญ์ ๋๋ค. ์ปดํฌ๋ํธ์ ๊ด์ฐฐ ๊ฐ๋ฅํ ๋์์ ์ง์คํ์ธ์.
์์: ์ํ ์ง์ ํ ์คํธ ํผํ๊ธฐ
ํน์ ์ํ ๋ณ์๊ฐ ์ ๋ฐ์ดํธ๋์๋์ง ํ ์คํธํ๋ ๋์ , ํด๋น ์ํ์ ๋ฐ๋ผ ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ฐ๋ฅธ ์ถ๋ ฅ์ ๋ ๋๋งํ๋์ง ํ ์คํธํ์ธ์. ์๋ฅผ ๋ค์ด, ์ปดํฌ๋ํธ๊ฐ ๋ถ์ธ ์ํ ๋ณ์์ ๋ฐ๋ผ ๋ฉ์์ง๋ฅผ ํ์ํ๋ ๊ฒฝ์ฐ, ์ํ ๋ณ์ ์์ฒด๋ฅผ ํ ์คํธํ๋ ๋์ ๋ฉ์์ง๊ฐ ํ์๋๊ฑฐ๋ ์จ๊ฒจ์ง๋์ง๋ฅผ ํ ์คํธํ์ธ์.
4. ํน์ ์ฌ๋ก์ `data-testid` ์ฌ์ฉํ๊ธฐ
์ผ๋ฐ์ ์ผ๋ก `data-testid` ์์ฑ์ ์ฌ์ฉํ์ง ์๋ ๊ฒ์ด ๊ฐ์ฅ ์ข์ง๋ง, ๋์์ด ๋ ์ ์๋ ํน์ ์ฌ๋ก๊ฐ ์์ต๋๋ค:
- ์๋ฏธ ์๋ ์๋ฏธ๊ฐ ์๋ ์์: ์๋ฏธ ์๋ ์ญํ , ๋ ์ด๋ธ ๋๋ ํ ์คํธ๊ฐ ์๋ ์์๋ฅผ ํ๊ฒํ ํด์ผ ํ๋ ๊ฒฝ์ฐ `data-testid`๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ๋ณต์กํ ์ปดํฌ๋ํธ ๊ตฌ์กฐ: ๋ณต์กํ ์ปดํฌ๋ํธ ๊ตฌ์กฐ์์ `data-testid`๋ ๊นจ์ง๊ธฐ ์ฌ์ด ์ ํ์์ ์์กดํ์ง ์๊ณ ํน์ ์์๋ฅผ ํ๊ฒํ ํ๋ ๋ฐ ๋์์ด ๋ ์ ์์ต๋๋ค.
- ์ ๊ทผ์ฑ ํ ์คํธ: `data-testid`๋ Cypress๋ Playwright์ ๊ฐ์ ๋๊ตฌ๋ก ์ ๊ทผ์ฑ ํ ์คํธ๋ฅผ ํ๋ ๋์ ํน์ ์์๋ฅผ ์๋ณํ๋ ๋ฐ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
์์: `data-testid` ์ฌ์ฉํ๊ธฐ
// MyComponent.js
import React from 'react';
function MyComponent() {
return (
This is my component.
);
}
export default MyComponent;
// MyComponent.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('์ปดํฌ๋ํธ ์ปจํ
์ด๋๋ฅผ ๋ ๋๋งํ๋ค', () => {
render( );
const containerElement = screen.getByTestId('my-component-container');
expect(containerElement).toBeInTheDocument();
});
});
์ค์: `data-testid`๋ ๋๋ฌผ๊ฒ, ๊ทธ๋ฆฌ๊ณ ๋ค๋ฅธ ์ฟผ๋ฆฌ ๋ฉ์๋๊ฐ ์ ํฉํ์ง ์์ ๋๋ง ์ฌ์ฉํ์ธ์.
5. ์๋ฏธ ์๋ ํ ์คํธ ์ค๋ช ์์ฑํ๊ธฐ
๋ช ํํ๊ณ ๊ฐ๊ฒฐํ ํ ์คํธ ์ค๋ช ์ ๊ฐ ํ ์คํธ์ ๋ชฉ์ ์ ์ดํดํ๊ณ ์คํจ๋ฅผ ๋๋ฒ๊น ํ๋ ๋ฐ ์ค์ํฉ๋๋ค. ํ ์คํธ๊ฐ ๋ฌด์์ ๊ฒ์ฆํ๋์ง ๋ช ํํ๊ฒ ์ค๋ช ํ๋ ์์ ์ ์ธ ์ด๋ฆ์ ์ฌ์ฉํ์ธ์.
์์: ์ข์ ํ ์คํธ ์ค๋ช vs. ๋์ ํ ์คํธ ์ค๋ช
๋์จ: `it('์๋ํ๋ค')`
์ข์: `it('์ฌ๋ฐ๋ฅธ ์ธ์ฌ ๋ฉ์์ง๋ฅผ ํ์ํ๋ค')`
๋ ์ข์: `it('name prop์ด ์ ๊ณต๋์ง ์์์ ๋ "Hello, World!" ์ธ์ฌ ๋ฉ์์ง๋ฅผ ํ์ํ๋ค')`
๋ ์ข์ ์์๋ ํน์ ์กฐ๊ฑด ํ์์ ์ปดํฌ๋ํธ์ ์์๋๋ ๋์์ ๋ช ํํ๊ฒ ์ค๋ช ํฉ๋๋ค.
6. ํ ์คํธ๋ฅผ ์๊ณ ์ง์ค์ ์ผ๋ก ์ ์งํ๊ธฐ
๊ฐ ํ ์คํธ๋ ์ปดํฌ๋ํธ ๋์์ ๋จ์ผ ์ธก๋ฉด์ ๊ฒ์ฆํ๋ ๋ฐ ์ง์คํด์ผ ํฉ๋๋ค. ์ฌ๋ฌ ์๋๋ฆฌ์ค๋ฅผ ๋ค๋ฃจ๋ ํฌ๊ณ ๋ณต์กํ ํ ์คํธ๋ฅผ ์์ฑํ์ง ๋ง์ธ์. ์๊ณ ์ง์ค๋ ํ ์คํธ๋ ์ดํดํ๊ณ , ์ ์ง๋ณด์ํ๊ณ , ๋๋ฒ๊น ํ๊ธฐ ๋ ์ฝ์ต๋๋ค.
7. ํ ์คํธ ๋์ญ(Mocks and Spies) ์ ์ ํ๊ฒ ์ฌ์ฉํ๊ธฐ
ํ ์คํธ ๋์ญ์ ํ ์คํธ ์ค์ธ ์ปดํฌ๋ํธ๋ฅผ ์์กด์ฑ์ผ๋ก๋ถํฐ ๊ฒฉ๋ฆฌํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค. ๋ชจ์(mock)์ ์คํ์ด(spy)๋ฅผ ์ฌ์ฉํ์ฌ ์ธ๋ถ ์๋น์ค, API ํธ์ถ ๋๋ ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ฅผ ์๋ฎฌ๋ ์ด์ ํ์ธ์.
์์: API ํธ์ถ ๋ชจํนํ๊ธฐ
// UserList.js
import React, { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
async function fetchUsers() {
const response = await fetch('/api/users');
const data = await response.json();
setUsers(data);
}
fetchUsers();
}, []);
return (
{users.map(user => (
- {user.name}
))}
);
}
export default UserList;
// UserList.test.js
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import UserList from './UserList';
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' },
]),
})
);
describe('UserList Component', () => {
it('์ฌ์ฉ์ ๋ชฉ๋ก์ ๊ฐ์ ธ์ ํ์ํ๋ค', async () => {
render( );
// ๋ฐ์ดํฐ๊ฐ ๋ก๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆผ
await waitFor(() => screen.getByText('John Doe'));
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
});
});
์ค๋ช :
- `global.fetch = jest.fn(...)`: `fetch` ํจ์๋ฅผ ๋ชจํนํ์ฌ ๋ฏธ๋ฆฌ ์ ์๋ ์ฌ์ฉ์ ๋ชฉ๋ก์ ๋ฐํํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ค์ API ์๋ํฌ์ธํธ์ ์์กดํ์ง ์๊ณ ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ ์ ์์ต๋๋ค.
- `await waitFor(() => screen.getByText('John Doe'))`: "John Doe" ํ ์คํธ๊ฐ ๋ฌธ์์ ๋ํ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค. ๋ฐ์ดํฐ๊ฐ ๋น๋๊ธฐ์ ์ผ๋ก ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ ์ด ๊ณผ์ ์ด ํ์ํฉ๋๋ค.
8. ์ฃ์ง ์ผ์ด์ค์ ์ค๋ฅ ์ฒ๋ฆฌ ํ ์คํธํ๊ธฐ
์ฑ๊ณต์ ์ธ ๊ฒฝ๋ก๋ง ํ ์คํธํ์ง ๋ง์ธ์. ์ฃ์ง ์ผ์ด์ค, ์ค๋ฅ ์๋๋ฆฌ์ค, ๊ฒฝ๊ณ ์กฐ๊ฑด์ ํ ์คํธํด์ผ ํฉ๋๋ค. ์ด๋ ์ ์ฌ์ ์ธ ๋ฌธ์ ๋ฅผ ์กฐ๊ธฐ์ ์๋ณํ๊ณ ์ปดํฌ๋ํธ๊ฐ ์๊ธฐ์น ์์ ์ํฉ์ ์ฐ์ํ๊ฒ ์ฒ๋ฆฌํ๋๋ก ๋ณด์ฅํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
์์: ์ค๋ฅ ์ฒ๋ฆฌ ํ ์คํธํ๊ธฐ
API์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ API ํธ์ถ์ด ์คํจํ๋ฉด ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํ์ํ๋ ์ปดํฌ๋ํธ๋ฅผ ์์ํด ๋ณด์ธ์. API ํธ์ถ์ด ์คํจํ์ ๋ ์ค๋ฅ ๋ฉ์์ง๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ํ์๋๋์ง ํ์ธํ๋ ํ ์คํธ๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
9. ์ ๊ทผ์ฑ์ ์ง์คํ๊ธฐ
์ ๊ทผ์ฑ์ ํฌ์ฉ์ ์ธ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋๋ ๋ฐ ์ค์ํฉ๋๋ค. RTL์ ์ฌ์ฉํ์ฌ ์ปดํฌ๋ํธ์ ์ ๊ทผ์ฑ์ ํ ์คํธํ๊ณ WCAG์ ๊ฐ์ ์ ๊ทผ์ฑ ํ์ค์ ์ถฉ์กฑํ๋์ง ํ์ธํ์ธ์. ๋ช ๊ฐ์ง ์ฃผ์ ์ ๊ทผ์ฑ ๊ณ ๋ ค ์ฌํญ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์๋งจํฑ HTML: ์๋งจํฑ HTML ์์(์: `
- ARIA ์์ฑ: ํนํ ์ปค์คํ ์ปดํฌ๋ํธ์ ๊ฒฝ์ฐ, ์์์ ์ญํ , ์ํ ๋ฐ ์์ฑ์ ๋ํ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด ARIA ์์ฑ์ ์ฌ์ฉํ์ธ์.
- ํค๋ณด๋ ๋ค๋น๊ฒ์ด์ : ๋ชจ๋ ์ํธ์์ฉ ์์๊ฐ ํค๋ณด๋ ๋ค๋น๊ฒ์ด์ ์ ํตํด ์ ๊ทผ ๊ฐ๋ฅํ์ง ํ์ธํ์ธ์.
- ์์ ๋๋น: ์ ์๋ ฅ ์ฌ์ฉ์๊ฐ ํ ์คํธ๋ฅผ ์ฝ์ ์ ์๋๋ก ์ถฉ๋ถํ ์์ ๋๋น๋ฅผ ์ฌ์ฉํ์ธ์.
- ์คํฌ๋ฆฐ ๋ฆฌ๋ ํธํ์ฑ: ์คํฌ๋ฆฐ ๋ฆฌ๋๋ก ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ์ฌ ์๊ฐ ์ฅ์ ๊ฐ ์๋ ์ฌ์ฉ์์๊ฒ ์๋ฏธ ์๊ณ ์ดํดํ ์ ์๋ ๊ฒฝํ์ ์ ๊ณตํ๋์ง ํ์ธํ์ธ์.
์์: `getByRole`๋ก ์ ๊ทผ์ฑ ํ ์คํธํ๊ธฐ
// MyAccessibleComponent.js
import React from 'react';
function MyAccessibleComponent() {
return (
);
}
export default MyAccessibleComponent;
// MyAccessibleComponent.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import MyAccessibleComponent from './MyAccessibleComponent';
describe('MyAccessibleComponent', () => {
it('์ฌ๋ฐ๋ฅธ aria-label์ ๊ฐ์ง ์ ๊ทผ์ฑ ์๋ ๋ฒํผ์ ๋ ๋๋งํ๋ค', () => {
render( );
const buttonElement = screen.getByRole('button', { name: 'Close' });
expect(buttonElement).toBeInTheDocument();
});
});
์ค๋ช :
- `screen.getByRole('button', { name: 'Close' })`: `getByRole`์ ์ฌ์ฉํ์ฌ ์ ๊ทผ์ฑ ์ด๋ฆ์ด "Close"์ธ ๋ฒํผ ์์๋ฅผ ์ฐพ์ต๋๋ค. ์ด๋ ๋ฒํผ์ด ์คํฌ๋ฆฐ ๋ฆฌ๋๋ฅผ ์ํด ์ฌ๋ฐ๋ฅด๊ฒ ๋ ์ด๋ธ๋ง๋์์์ ๋ณด์ฅํฉ๋๋ค.
10. ๊ฐ๋ฐ ์ํฌํ๋ก์ฐ์ ํ ์คํธ ํตํฉํ๊ธฐ
ํ ์คํ ์ ๋์ค์ ์๊ฐํ ๊ฒ์ด ์๋๋ผ ๊ฐ๋ฐ ์ํฌํ๋ก์ฐ์ ํ์์ ์ธ ๋ถ๋ถ์ด์ด์ผ ํฉ๋๋ค. ์ฝ๋๊ฐ ์ปค๋ฐ๋๊ฑฐ๋ ๋ฐฐํฌ๋ ๋๋ง๋ค ํ ์คํธ๊ฐ ์๋์ผ๋ก ์คํ๋๋๋ก CI/CD ํ์ดํ๋ผ์ธ์ ํ ์คํธ๋ฅผ ํตํฉํ์ธ์. ์ด๋ ๋ฒ๊ทธ๋ฅผ ์กฐ๊ธฐ์ ๋ฐ๊ฒฌํ๊ณ ํ๊ท๋ฅผ ๋ฐฉ์งํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
11. ํ์งํ ๋ฐ ๊ตญ์ ํ(i18n) ๊ณ ๋ คํ๊ธฐ
๊ธ๋ก๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฝ์ฐ, ํ ์คํ ์ค์ ํ์งํ ๋ฐ ๊ตญ์ ํ(i18n)๋ฅผ ๊ณ ๋ คํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์ปดํฌ๋ํธ๊ฐ ๋ค๋ฅธ ์ธ์ด์ ๋ก์ผ์ผ์์ ์ฌ๋ฐ๋ฅด๊ฒ ๋ ๋๋ง๋๋์ง ํ์ธํ์ธ์.
์์: ํ์งํ ํ ์คํธํ๊ธฐ
ํ์งํ๋ฅผ ์ํด `react-intl`์ด๋ `i18next`์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ํ ์คํธ์์ ํ์งํ ์ปจํ ์คํธ๋ฅผ ๋ชจํนํ์ฌ ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ฐ๋ฅธ ๋ฒ์ญ๋ ํ ์คํธ๋ฅผ ํ์ํ๋์ง ํ์ธํ ์ ์์ต๋๋ค.
12. ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ค์ ์ ์ํด ์ปค์คํ ๋ ๋ ํจ์ ์ฌ์ฉํ๊ธฐ
๋ ํฐ ํ๋ก์ ํธ์์ ์์ ํ ๋, ์ฌ๋ฌ ํ ์คํธ์์ ๋์ผํ ์ค์ ๋จ๊ณ๋ฅผ ๋ฐ๋ณตํ๋ ์์ ์ ๋ฐ๊ฒฌํ ์ ์์ต๋๋ค. ์ค๋ณต์ ํผํ๊ธฐ ์ํด ๊ณตํต ์ค์ ๋ก์ง์ ์บก์ํํ๋ ์ปค์คํ ๋ ๋ ํจ์๋ฅผ ๋ง๋์ธ์.
์์: ์ปค์คํ ๋ ๋ ํจ์
// test-utils.js
import React from 'react';
import { render } from '@testing-library/react';
import { ThemeProvider } from 'styled-components';
import theme from './theme';
const AllTheProviders = ({ children }) => {
return (
{children}
);
}
const customRender = (ui, options) =>
render(ui, { wrapper: AllTheProviders, ...options })
// ๋ชจ๋ ๊ฒ์ ๋ค์ ๋ด๋ณด๋ด๊ธฐ
export * from '@testing-library/react'
// render ๋ฉ์๋ ์ค๋ฒ๋ผ์ด๋
export { customRender as render }
// MyComponent.test.js
import React from 'react';
import { render, screen } from './test-utils'; // ์ปค์คํ
๋ ๋ ๊ฐ์ ธ์ค๊ธฐ
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('ํ
๋ง์ ํจ๊ป ์ฌ๋ฐ๋ฅด๊ฒ ๋ ๋๋ง๋๋ค', () => {
render( );
// ์ฌ๊ธฐ์ ํ
์คํธ ๋ก์ง ์์ฑ
});
});
์ด ์์ ๋ ์ปดํฌ๋ํธ๋ฅผ ThemeProvider๋ก ๊ฐ์ธ๋ ์ปค์คํ ๋ ๋ ํจ์๋ฅผ ๋ง๋ญ๋๋ค. ์ด๋ฅผ ํตํด ๋ชจ๋ ํ ์คํธ์์ ThemeProvider ์ค์ ์ ๋ฐ๋ณตํ ํ์ ์์ด ํ ๋ง์ ์์กดํ๋ ์ปดํฌ๋ํธ๋ฅผ ์ฝ๊ฒ ํ ์คํธํ ์ ์์ต๋๋ค.
๊ฒฐ๋ก
React Testing Library๋ ์ปดํฌ๋ํธ ํ ์คํ ์ ๊ฐ๋ ฅํ๊ณ ์ฌ์ฉ์ ์ค์ฌ์ ์ธ ์ ๊ทผ ๋ฐฉ์์ ์ ๊ณตํฉ๋๋ค. ์ด๋ฌํ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฅด๋ฉด, ์ฌ์ฉ์ ํ๋๊ณผ ์ ๊ทผ์ฑ์ ์ด์ ์ ๋ง์ถ ์ ์ง๋ณด์ ๊ฐ๋ฅํ๊ณ ํจ๊ณผ์ ์ธ ํ ์คํธ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค. ์ด๋ ๊ธ๋ก๋ฒ ์ฌ์ฉ์๋ฅผ ์ํ ๋ ๊ฒฌ๊ณ ํ๊ณ ์ ๋ขฐํ ์ ์์ผ๋ฉฐ ํฌ์ฉ์ ์ธ React ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ์ด์ด์ง ๊ฒ์ ๋๋ค. ์ฌ์ฉ์ ์ํธ์์ฉ์ ์ฐ์ ์ํ๊ณ , ๊ตฌํ ์ธ๋ถ ์ฌํญ ํ ์คํธ๋ฅผ ํผํ๊ณ , ์ ๊ทผ์ฑ์ ์ง์คํ๊ณ , ๊ฐ๋ฐ ์ํฌํ๋ก์ฐ์ ํ ์คํ ์ ํตํฉํ๋ ๊ฒ์ ๊ธฐ์ตํ์ธ์. ์ด๋ฌํ ์์น์ ๋ฐ์๋ค์์ผ๋ก์จ ์ ์ธ๊ณ ์ฌ์ฉ์์ ์๊ตฌ๋ฅผ ์ถฉ์กฑํ๋ ๊ณ ํ์ง React ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
ํต์ฌ ์์ฝ:
- ์ฌ์ฉ์ ์ํธ์์ฉ์ ์ง์คํ๊ธฐ: ์ฌ์ฉ์๊ฐ ์ํธ์์ฉํ๋ ๊ฒ์ฒ๋ผ ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ์ธ์.
- ์ ๊ทผ์ฑ ์ฐ์ ์์: ์ปดํฌ๋ํธ๊ฐ ์ฅ์ ๋ฅผ ๊ฐ์ง ์ฌ์ฉ์์๊ฒ ์ ๊ทผ ๊ฐ๋ฅํ์ง ํ์ธํ์ธ์.
- ๊ตฌํ ์ธ๋ถ ์ฌํญ ํผํ๊ธฐ: ๋ด๋ถ ์ํ๋ ํจ์ ํธ์ถ์ ํ ์คํธํ์ง ๋ง์ธ์.
- ๋ช ํํ๊ณ ๊ฐ๊ฒฐํ ํ ์คํธ ์์ฑํ๊ธฐ: ํ ์คํธ๋ฅผ ์ดํดํ๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ฝ๊ฒ ๋ง๋์ธ์.
- ์ํฌํ๋ก์ฐ์ ํ ์คํ ํตํฉํ๊ธฐ: ํ ์คํธ๋ฅผ ์๋ํํ๊ณ ์ ๊ธฐ์ ์ผ๋ก ์คํํ์ธ์.
- ๊ธ๋ก๋ฒ ์ฌ์ฉ์ ๊ณ ๋ คํ๊ธฐ: ์ปดํฌ๋ํธ๊ฐ ๋ค๋ฅธ ์ธ์ด์ ๋ก์ผ์ผ์์ ์ ์๋ํ๋์ง ํ์ธํ์ธ์.